大家早安~
中秋連假還是不能停更!在吃烤肉的同時還是來產出一下🙂↕️
那今天我們要延續昨天聊到的 Word2Vec!昨天我們已經知道它能透過上下文學出詞與詞之間的語意關係,
像「貓」和「狗」在向量空間裡就會靠得比較近,而「國王」和「皇后」也會比「國王」和「香蕉」更相似,我們也提到過它的兩種訓練方式,並在最後說到一個小小的限制——那就是 每個詞的向量是固定的!
也就是說,不管「吃」是出現在「吃飯」還是「吃網速」裡,在 Word2Vec 看來,它永遠都是同一個「吃」。
但為什麼會這樣呢?
原因是因為 Word2Vec 屬於靜態詞嵌入(static embedding),也就是每個詞在訓練結束後都只會被賦予一個固定的向量,不會隨著上下文改變而變化,這樣的方式雖然已經比早期的 One-hot 或 Bag of Words 聰明很多,但畢竟語言是充滿歧異的,我們還是會希望電腦能夠從語境(context)的情況下推敲出真正的語意,所以就出現了 contextual embedding(語境式詞嵌入) 的概念啦!
顧名思義,它會根據「上下文」來決定詞的向量表示,也就是說,同樣的詞在不同句子中,會有不同的向量,我們以下面兩句話為例:
我打開了門
我打開了電腦
在這兩個句子中,「打開」的意義明顯不同,contextual embedding 會讓模型產生兩個不同的「打開」向量,讓模型能更準確地捕捉語意差異。
contextual embedding 怎麼來?
要能生成這種具語境的詞向量,通常會透過 深度學習模型(deep learning models) 來完成。
這些模型會同時處理整個輸入句子(也就是一個「序列」),並考慮詞與詞之間的關聯性,最後產出每個詞的動態向量表示。
常見能做到這件事的模型包括:
我們前面有介紹過一些模型像是SVM等,都是屬於傳統機器學習模型,跟這邊提到的深度學習不一樣喔!
那他們的原理蠻複雜,我們今天就先暫時不提,之後會再介紹給大家!
我們直接來看看如何用word2vec 以及BERT 來訓練不同的詞向量~
word2vec
首先是要安裝必要的套件
from gensim.models import Word2Vec
再來把我們想訓練的語料經過斷詞後,開始訓練模型
sentences = [["我", "喜歡", "狗"], ["我", "討厭", "狗"], ["狗", "是", "動物"]] #這邊是已經斷詞好的句子
#開始訓練word2vec
w2v_model = Word2Vec(
sentences, #訓練的語料
vector_size=50, #每個詞會學到一個 50 維的向量(embedding)。維度越大,能表達的語意越豐富,但需要更多資料支撐。
window=2, #對每個中心詞,最多看「左 2 + 右 2」的鄰近詞當作 上下文(context)
min_count=1, # 詞頻門檻
# sg=0 #(可選)訓練方式:0=CBOW、1=Skip-gram)
)
這樣就訓練好惹!是不是超快!!
接下來我們可以來看看狗的向量
print(w2v_model.wv["狗"]) # 會取出「狗」這個詞的 embedding 向量
輸出結果:
[-1.0724545e-03 4.7286271e-04 1.0206699e-02 1.8018546e-02
-1.8605899e-02 -1.4233618e-02 1.2917745e-02 1.7945977e-02
-1.0030856e-02 -7.5267432e-03 1.4761009e-02 -3.0669428e-03
-9.0732267e-03 1.3108104e-02 -9.7203208e-03 -3.6320353e-03
5.7531595e-03 1.9837476e-03 -1.6570430e-02 -1.8897636e-02
1.4623532e-02 1.0140524e-02 1.3515387e-02 1.5257311e-03
1.2701781e-02 -6.8107317e-03 -1.8928028e-03 1.1537147e-02
-1.5043275e-02 -7.8722071e-03 -1.5023164e-02 -1.8600845e-03
1.9076237e-02 -1.4638334e-02 -4.6675373e-03 -3.8754821e-03
1.6154874e-02 -1.1861792e-02 9.0324880e-05 -9.5074680e-03
-1.9207101e-02 1.0014586e-02 -1.7519170e-02 -8.7836506e-03
-7.0199967e-05 -5.9236289e-04 -1.5322480e-02 1.9229487e-02
9.9641159e-03 1.8466286e-02]
我們可以用相似度來看看狗
跟動物
以及狗
跟香蕉
語意向量上有多接近(應該是狗跟動物會比狗跟香蕉相近比較符合常理吧xd)
print(w2v_model.wv.similarity("狗", "動物"))
print(w2v_model.wv.similarity("狗", "香蕉"))
結果
0.13204393
0.012442183
將將~ 沒錯!在這邊的例子中可以看到"狗"跟"動物"在語意相似度會比"狗"跟"香蕉"來得高!
大家也可以在是其他組比較看看!
那我們接著來看看如何訓練可以產出contextual embedding 的方式吧!
BERT
首先一樣先載入套件並引入bert
from transformers import AutoTokenizer, AutoModel
import torch
# 載入我們的模型
tokenizer = AutoTokenizer.from_pretrained("bert-base-chinese")
model = AutoModel.from_pretrained("bert-base-chinese")
把語料放進來
#要訓練的語料
docs = [
"我喜歡吃炸雞",
"這個遊戲很吃網速",
"想要吃麥當勞",
"這家餐廳很好吃"
]
設定目標詞
target_word = "吃"
vectors = [] # 存 target_word 的向量(tensor)
contexts = [] # 存含有 target_word 的句子(str)
得到詞向量後我們來看看我喜歡吃炸雞
中的吃
以及這個遊戲很吃網速
中的`吃 語意相似度為何
with torch.no_grad():
for s in docs:
inputs = tokenizer(s, return_tensors="pt")
outputs = model(**inputs)
tokens = tokenizer.tokenize(s)
for i, t in enumerate(tokens):
if t == target_word:
vec = outputs.last_hidden_state[0, i+1].detach().cpu().numpy()
vectors.append(vec)
# 比較第一句與第二句的「吃」語意相似度
sim = cosine_similarity([vectors[0]], [vectors[1]])[0][0]
print(f"第一句 vs 第二句『吃』的相似度:{sim:.4f}")
輸出結果第一句 vs 第二句『吃』的相似度:0.4388
那我喜歡吃炸雞
跟 想要吃麥當勞
呢?
sim = cosine_similarity([vectors[0]], [vectors[2]])[0][0]
print(f"第一句 vs 第三句『吃』的相似度:{sim:.4f}")
輸出結果
第一句 vs 第三句『吃』的相似度:0.8466
將將將~~有沒有看到!就算是同一個「吃」在不同語境底下也有不同的向量表示,這就是contextual embedding的厲害啦!!
那今天就先到這邊嚕~明天見!